.binarymode SegaGG
.sdsctag 1.0, "Sega Tween", "SMS/Game Gear Demo", "Ben Ryves"
.exportmode emukonpatch
.export

.defpage 0, 32*1024, $0000
.page 0

.varloc $C000, $2000 ; 8KB on-board RAM

.local
.nestmodules


.module Config

	.enum Platform, GG, SMS
	Platform = Platform.[%extension%]
	
	.enum Mode, Normal, 3D
	Mode = Mode.[%variation%]
	
	.enum Quality, Low, High
	Quality = Quality.High
	
	DelayBetweenTweens = 48
	TweenSpeed = 16
	
	Glasses3DOffset = 2200
	Glasses2DOffset = -Glasses3DOffset / 220
	
	BandScaleFactor = 4

.endmodule



.module Boot
	.org $00
	di
	im 1
	jp Program.Main
.endmodule


.module Interrupt	
	.org $38
	
	
	; Frame interrupt.

	push ix
	push iy
	ex af,af'
	exx

	call Program.Interrupt
	
	exx
	ex af,af'
	pop iy
	pop ix

	ei
	ret
.endmodule

.module Pause
	.org $66
	reti
.endmodule

; Code libraries



.include "Video.asm"
.include "Maths.asm"
.include "TileMap.asm"
.include "Sprites.asm"

.asciimap "!", 'Z'+2, ($80+{*})-'!'
.asciimap 'a', 'z', ({*}-'a')+"A"
.asciimap ' ', "!"-1

.module Program
	
; Entry point
Main

; Reset paging
	
	xor a
	ld ($FFFC),a 
	ld ($FFFD),a
	inc a
	ld ($FFFE),a
	inc a 
	ld ($FFFF),a
	
; Bung SP somewhere useful
	
	ld sp,$DFF0
	
; Reset video
	
	call Video.Reset



.struct ModelRotation
	.var byte, X
	.var byte, Y
	.var byte, dX
	.var byte, dY
.endstruct

.struct Point3D
	.var byte, X
	.var byte, Y
	.var byte, Z
.endstruct

.struct PosBufferEntry
	.var ubyte, X
	.var ubyte, Y
	.var ubyte, Depth
	.var ubyte, Colour
.endstruct

.tvar ModelRotation, Rotation
		
.tvar uword, WritingPositionBuffer
.tvar uword, ReadingPositionBuffer

.tvar ubyte, Colour

.tvar Point3D[40], Model ; Current model

.tvar uword, Model1 ; Pointer to CURRENT model data
.tvar uword, Model2 ; Pointer to NEXT model data

.tvar ubyte, TweenAmount
.tvar ubyte, TweenDelay

.tvar Point3D[40], BlankModel ; Temp model with all points at (0,0,0)

.tvar word[16], PlasmaPalette

.tvar word[16], MirrorVertically

.tvar byte, RedPhase
.tvar byte, GreenPhase
.tvar byte, BluePhase

.tvar byte[2048], SpriteOut

.tvar ubyte, BackgroundTimerDelay

Reset
	call Video.ScreenOff
	
	xor a
	ld b,Video.Reg.OverscanColour
	call Video.SetReg
	
	
	ld hl,Resources.Background
	ld bc,0
	call TileMap.Load
	
	; Mirror vertically
					
	ld ix,Video.NameTable-64
	ld iy,Video.NameTable+24*64

	ld b,12
--	push bc
	push ix \ pop hl
	ld de,64
	add hl,de
	push hl \ pop ix

	call Video.GotoHLRead
	
	ld hl,MirrorVertically
	ld b,16

-	in a,($BE)
	ld (hl),a \ inc hl
	in a,($BE)
	xor $04
	ld (hl),a \ inc hl
	djnz {-}
	
	push iy \ pop hl
	ld de,-64
	add hl,de
	push hl \ pop iy
	
	call Video.GotoHL
	ld hl,MirrorVertically
	ld b,32
	ld c,$BE
	otir
	
	pop bc
	djnz {--}

	; Mirror horizontally
	
	ld hl,Video.NameTable-1
	call Video.GotoHLRead
			
	ld c,24

--	ld b,16

-	in a,($BE)
	ld e,a
	in a,($BE)
	ld d,a
	push de
	djnz {-}
	
	ld b,16
-	ld a,d
	out ($BE),a
	ld a,e
	xor $02
	out ($BE),a
	pop de
	djnz {-}
	
	dec c
	jr nz,{--}
	
	
	ld hl,PlasmaPalette
	ld (hl),0
	ld d,h \ ld e,l
	inc de
	ld bc,31
	ldir
	
	xor a
	call Video.GotoPalette
	xor a
	ld b,32
-	out ($BE),a
	djnz {-}
	
	ld a,-64
	ld (RedPhase),a
	ld (GreenPhase),a
	ld (BluePhase),a

	ld hl,Resources.Sprites
	ld a,(Config.Platform == Config.Platform.GG) ? 32 : 16
	ld d,$40+(380/8)
	call Sprites.LoadDirect
			
	ld hl,BlankModel \ ld (Model1),hl
	ld hl,Resources.Models+00*3 \ ld (Model2),hl	
	
	ld hl,5+(3<<8)	
	ld (Rotation.dX),hl
	
	ld hl,$6040
	ld (Rotation.X),hl
	
	xor a
	ld (TweenAmount),a
	
	ld a,3
	ld (TweenDelay),a
	
	ld hl,BlankModel
	ld de,BlankModel+1
	ld (hl),0
	ld bc,36*3-1
	ldir
	
	ld hl,BlankModel
	ld de,Model
	ld bc,36*3
	ldir
	
	
	ld hl,Video.SpriteTable
	call Video.GotoHL
	ld b,128
-	ld a,$D0
	out ($BE),a
	djnz {-}
	
	.if Config.Mode == Config.Mode.3D
		ld a,Video.Reg.Mode1.MaskColumn0 | Video.Reg.Mode1.Mode4
		ld b,Video.Reg.Mode1
		call Video.SetReg
	.endif
	
	ld a,Video.Reg.Mode2.Enable | Video.Reg.Mode2.ZoomSprites | Video.Reg.Mode2.VBlankInterrupt
	ld b,Video.Reg.Mode2
	call Video.SetReg
	

	ld hl,SpriteOut
	ld (WritingPositionBuffer),hl
	ld (ReadingPositionBuffer),hl
	
DrawModel

	ld a,(TweenAmount)
	or a
	jp nz,AmTweening
	
	ld a,(TweenDelay)
	cp Config.DelayBetweenTweens
	jr nz,{+}
	ld hl,(Model2)
	ld (Model1),hl
	ld de,Model
	ld bc,36*3
	ldir
	ld (Model2),hl
	ld de,Resources.EndOfModels
	or a
	sbc hl,de
	jr nz,{+}
	ld hl,Resources.Models
	ld (Model2),hl
+
	dec a
	ld (TweenDelay),a
	jp nz,HandledTweening
	
	ld a,Config.TweenSpeed
	ld (TweenAmount),a		
AmTweening
	
	ld ix,(Model2)
	ld iy,(Model1)
	ld hl,Model
	
	ld b,36*3
TweenLoop
	push bc
	push hl

	ld e,(ix)
	ld a,(TweenAmount)
	call Maths.Mul.S8U8
	push hl
	
	ld e,(iy)
	ld a,(TweenAmount)
	neg
	call Maths.Mul.S8U8 
	pop de
	
	add hl,de
	
	ld a,h		
	pop hl
	ld (hl),a
	inc hl
	inc ix
	inc iy
	pop bc
	djnz TweenLoop
	
	ld a,(TweenAmount)
	add a,Config.TweenSpeed
	ld (TweenAmount),a
	jp nz,HandledTweening

	

	ld a,Config.DelayBetweenTweens 
	ld (TweenDelay),a
			
HandledTweening	

	ld ix,Rotation.dX

	in a,($DC)
	
	bit 2,a \ jr nz,{+} \ inc (ix+0) \ +
	bit 3,a \ jr nz,{+} \ dec (ix+0) \ +
	bit 0,a \ jr nz,{+} \ inc (ix+1) \ +
	bit 1,a \ jr nz,{+} \ dec (ix+1) \ +
	
	bit 4,a \ jr nz,{+} \ push af \ ld a,1 \ ld (TweenDelay),a \ pop af \ +
	bit 5,a \ jr nz,{+} \ ld (ix+0),0 \ ld (ix+1),0 \ +

	; Rotate

	ld a,(Rotation.X)
	add a,(ix+0)
	ld (Rotation.X),a
	
	ld a,(Rotation.Y)
	add a,(ix+1)
	ld (Rotation.Y),a

	; Rotate and transform all the points
	
	.tvar byte, SinRotX
	.tvar byte, SinRotY
	.tvar byte, CosRotX
	.tvar byte, CosRotY
			
	ld a,(Rotation.X) \ call Maths.Trig.Sin \ ld (SinRotX),a
	ld a,(Rotation.Y) \ call Maths.Trig.Sin \ ld (SinRotY),a
	ld a,(Rotation.X) \ call Maths.Trig.Cos \ ld (CosRotX),a
	ld a,(Rotation.Y) \ call Maths.Trig.Cos \ ld (CosRotY),a
	
	xor a
	ld (Colour),a
	
	; Clear what we're writing to:
		
	ld hl,(WritingPositionBuffer)
	ld (hl),0
	ld d,h \ ld e,l
	inc de
	ld bc,36*4-1
	ldir
		
	ld hl,(WritingPositionBuffer)
	ld (EndWritePointer),hl
	ld a,-1
	ld (PointsSoFar),a
		
	ld b,36 ; 36 blobs in all!
	ld ix,Model
PointLoop
	push bc
	
	
	; ix+0->x
	; ix+1->y
	; ix+2->z
	
	; rX = (x * SinRotX) + (y * CosRotX)
	; rY = (x * CosRotX * SinRotY) - ((y * SinRotX * SinRotY) + (z * CosRotY))
	; rZ = (x * CosRotX * CosRotY) - ((y * SinRotX * CosRotY) - (z * SinRotY))
	
	.tvar word, XCosRotX ; \_ These both crop up twice!
	.tvar word, YSinRotX ; /

	ld b,(ix+0)
	ld a,(CosRotX) \ ld e,a
	call Maths.Mul.S8S8
	ld (XCosRotX),hl
	
	ld b,(ix+1)
	ld a,(SinRotX) \ ld e,a
	call Maths.Mul.S8S8
	ld (YSinRotX),hl
	
	; rZ
	
	ld b,(ix+2)
	ld a,(SinRotY) \ ld e,a
	call Maths.Mul.S8S8
	push hl		
		ld de,(YSinRotX)
		ld a,(CosRotY)
		call Maths.Mul.S16S8
		add hl,hl \ rl a
		ld l,h \ ld h,a
	pop de
	or a
	sbc hl,de
	push hl
		ld de,(XCosRotX)
		ld a,(CosRotY)
		call Maths.Mul.S16S8
		add hl,hl \ rl a
		ld l,h \ ld h,a
		or a
	pop de
	sbc hl,de
	ld a,80
	add a,h
	
	.tvar ubyte, PointsSoFar
	.tvar uword, EndWritePointer

	
	ld c,a
	
	ld a,(PointsSoFar)
	inc a
	ld (PointsSoFar),a
	jp nz,NotFirstPoint
		ld a,c
		ld iy,(WritingPositionBuffer)
		jp SetSortedAddress
	
NotFirstPoint
	ld b,a
	
	ld a,c
	push af

	ld hl,(WritingPositionBuffer)
	ld de,4
-	cp (hl)
	jp c,FoundSlotForPoint
	add hl,de
	djnz {-}
FoundSlotForPoint
	push hl

	ld hl,(EndWritePointer)
	add hl,de
	ld (EndWritePointer),hl
	
	inc b
	ld a,b
	add a,a
	add a,a
	ld c,a
	ld b,0
	push hl
	add hl,de
	pop de
	ex de,hl
	
	.if Config.Mode == Config.Mode.3D
		push bc
		push de
		push hl
		inc h
		inc d
		lddr
		pop hl
		pop de
		pop bc
	.endif
	
	lddr
			
	pop iy
	pop af
SetSortedAddress
	
	
	ld c,a
	ld (iy+0),a
	
	.if Config.Mode == Config.Mode.3D
		inc iyh \ ld (iy+0),a \ dec iyh
	.endif
	
	; Colour
	ld b,$78
	cp 48 \ jr c,SetPointSize
	inc b
	cp 64 \ jr c,SetPointSize
	inc b
	cp 92 \ jr c,SetPointSize
	inc b
SetPointSize
	ld a,(Colour)
	add a,b
	ld (iy+3),a
	
	.if Config.Mode == Config.Mode.3D
		inc iyh \ ld (iy+3),a \ dec iyh
	.endif
		
	ld hl,$6800
	call Maths.Div.U16S8
	push hl
	push hl
	
	.if Config.Mode == Config.Mode.3D
		push hl ; We need it 3 times!
	.endif
	
	; (Inverse depth is now on the stack)

	; rX
	
	ld b,(ix+0)
	ld a,(SinRotX) \ ld e,a
	call Maths.Mul.S8S8
	push hl		
		ld b,(ix+1)
		ld a,(CosRotX) \ ld e,a
		call Maths.Mul.S8S8
	pop de
	add hl,de
	
	.if Config.Mode == Config.Mode.3D
		ld de,Config.Glasses3DOffset
		add hl,de
	.endif
	
	pop de ; DEpth :)
	
	.if Config.Mode == Config.Mode.3D
		push hl ; We need our rotated X for later.
	.endif
	
	.if Config.Quality == Config.Quality.High
		ld b,h \ ld c,l
		call Maths.Mul.S16S16 ; sDEHL = sDE*sBC		
		ld a,160/2+48-8
		add a,e
	.else
		ld a,h
		call Maths.Mul.S16S8 ; sAHL = sDE*sA
		ld a,160/2+48-8
		add a,h
	.endif
	
	.if Config.Mode == Config.Mode.3D
		ld b,Config.Glasses2DOffset
		add a,b
	.endif
	
	ld (iy+1),a
	
	.if Config.Mode == Config.Mode.3D
		; We need to do that again for the other side:
		
		pop hl ; Rotated camera
		
		ld de,-Config.Glasses3DOffset*2
		add hl,de
		
		pop de ; DEpth
		
		.if Config.Quality == Config.Quality.High
			ld b,h \ ld c,l
			call Maths.Mul.S16S16 ; sDEHL = sDE*sBC		
			ld a,160/2+48-8
			add a,e
		.else
			ld a,h
			call Maths.Mul.S16S8 ; sAHL = sDE*sA
			ld a,160/2+48-8
			add a,h
		.endif
		
		ld b,-Config.Glasses2DOffset
		add a,b
		
		inc iyh \ ld (iy+1),a \ dec iyh
	.endif
	
	; rY
	
	ld b,(ix+2)
	ld a,(CosRotY) \ ld e,a
	call Maths.Mul.S8S8
	push hl		
		ld de,(YSinRotX)
		ld a,(SinRotY)
		call Maths.Mul.S16S8
		add hl,hl \ rl a
		ld l,h \ ld h,a
	pop de
	add hl,de
	push hl
		ld de,(XCosRotX)
		ld a,(SinRotY)
		call Maths.Mul.S16S8
		add hl,hl \ rl a
		ld l,h \ ld h,a
		or a
	pop de
	sbc hl,de
	
	pop de


	.if Config.Quality == Config.Quality.High
		ld b,h \ ld c,l
		call Maths.Mul.S16S16 ; sDEHL = sDE*sBC		
		ld a,144/2+24-8
		add a,e
	.else
		ld a,h
		call Maths.Mul.S16S8 ; sAHL = sDE*sA
		ld a,144/2+24-8
		add a,h
	.endif
	
	ld (iy+2),a	

	.if Config.Mode == Config.Mode.3D
		inc iyh \ ld (iy+2),a \ dec iyh
	.endif
	
	inc ix
	inc ix
	inc ix
	
	pop bc
	ld a,b
	cp 19
	jr nz,{+}
	ld a,4
	ld (Colour),a
+
	dec b
	jp nz,PointLoop
	
	; We have FINISHED drawing the model!
	di
	
	ld hl,(WritingPositionBuffer)
	
	push hl	
	pop hl
	
	ld a,(WritingPositionBuffer+1)
	xor Config.Mode.3D ? 2 : 1
	ld (WritingPositionBuffer+1),a

	ld (ReadingPositionBuffer),hl

	ei
	
	jp DrawModel
	
	
Interrupt
	ld hl,Video.SpriteTable+128
	call Video.GotoHL
	
	ld hl,(ReadingPositionBuffer)
	ld a,l
	or h
	jp z,NoSpriteDataToRead
	
		
	inc hl
	push hl
	ld b,36
	
-	ld a,(hl) \ inc hl \ out ($BE),a
	inc hl
	ld a,(hl) \ inc hl \ out ($BE),a
	inc hl
	djnz {-}
	
	ld hl,Video.SpriteTable
	call Video.GotoHL
	
	pop hl
	inc hl  ; Y,C
	
	ld de,4
	ld b,36
-	ld a,(hl) \ out ($BE),a
	add hl,de
	djnz {-}
	
	.if Config.Mode == Config.Mode.Normal
		ld hl,0
		ld (ReadingPositionBuffer),hl
	.else
		
		ld a,(ReadingPositionBuffer+1)
		xor 1
		ld (ReadingPositionBuffer+1),a
		and 1
		ld ($FFF8),a
		ld a,8
		jr z,{+}
		xor a
	+	ld b,Video.Reg.ScrollX
		call Video.SetReg
	.endif
	
NoSpriteDataToRead
	ld a,(BackgroundTimerDelay)
	inc a
	ld (BackgroundTimerDelay),a
	and 3
	ret nz
	
	xor a
	call Video.GotoPalette
	ld hl,Program.PlasmaPalette
	ld b,Config.Platform == Config.Platform.GG ? 32 : 16
	ld c,$BE
	otir


	ld hl,Program.PlasmaPalette + (Config.Platform == Config.Platform.GG ? 2 : 1)
	ld de,Program.PlasmaPalette
	ld bc,Config.Platform == Config.Platform.GG ? 30 : 15
	ldir
			

	ld a,(Program.GreenPhase)
	add a,2*Config.BandScaleFactor
	ld (Program.GreenPhase),a
	call Maths.Trig.Sin
	sub 128
	.if Config.Platform == Config.Platform.GG
		and $F0
	.else
		srl a
		srl a
		srl a
		srl a
		and %00001100
	.endif
	ld b,a
	
	ld a,(Program.RedPhase)
	add a,3*Config.BandScaleFactor
	ld (Program.RedPhase),a
	call Maths.Trig.Sin
	sub 128
	.if Config.Platform == Config.Platform.GG
		srl a
		srl a
		srl a
		srl a
	.else
		rlca
		rlca
		and 3
	.endif
	or b	

	.if Config.Platform == Config.Platform.GG
		ld (de),a
		inc de
	.else
		ld b,a
	.endif
	
	ld a,(Program.BluePhase)
	add a,7*Config.BandScaleFactor
	ld (Program.BluePhase),a
	call Maths.Trig.Sin
	sub 128
	.if Config.Platform == Config.Platform.GG
		srl a
		srl a
		srl a
		srl a
	.else
		srl a
		srl a
		and %00110000
		or b
	.endif

	ld (de),a
	ret
	

.module Resources

	Background
	.if Config.Platform == Config.Platform.GG
		.incbin "Resources/Gradient-GG.res"
	.else
		.incbin "Resources/Gradient-SMS.res"
	.endif	
	
	Sprites .incbin "Resources/Point.res"
	Models .include "Resources/Models.inc"	
.endmodule


.echo $,"B: ", int(100-($*100)/(32*1024)), "% free.\n"
	
.endmodule